#include		<stdlib.h>
#include		<stdio.h>
#include		<elf.h>
#include		<libelf.h>
#include		<gelf.h>
#include		<sys/types.h>
#include		<sys/stat.h>
#include		<fcntl.h>
#include		<string.h>
#include		<unistd.h>

#include		"ftrace.h"
#include		"list.h"

Elf			*read_elf(int *fd, char *filename)
{
  Elf			*e;

  e = NULL;
  if (-1 == (*fd = open(filename, O_RDONLY)))
    fprintf(stderr, "open %s failed", filename);
  else if (elf_version(EV_CURRENT) == EV_NONE)
    fprintf(stderr, "ELF library initialization failed: %s\n",
	elf_errmsg(-1));
  else if (NULL == (e = elf_begin(*fd, ELF_C_READ, NULL)))
    fprintf(stderr, "elf_begin() failed: %s\n",
	elf_errmsg(-1));
  else if ((ELF_K_ELF != elf_kind(e)) ||
      (ELFCLASS32 != gelf_getclass(e) && ELFCLASS64 != gelf_getclass(e)))
  {
    fprintf(stderr, "not an ELF i386 or x86_64 object\n");
    elf_end(e);
    close(*fd);
    exit(1);
  }
  if (-1 != *fd && !e)
    close(*fd);
  return (e);
}

int			get_symdata(Elf *e, Elf_Data **symtab_data,
    Elf_Data **strtab_data)
{
  char			*name;
  Elf_Scn		*scn;
  GElf_Shdr		shdr;
  size_t		shstrndx;

  if (elf_getshdrstrndx(e, &shstrndx))
  {
    fprintf(stderr, "elf_getshdrstrndx() failed: %s.", elf_errmsg(-1));
    return (-1);
  }
  scn = NULL;
  while ((scn = elf_nextscn(e, scn)))
    if (NULL == gelf_getshdr(scn, &shdr))
      return (-1);
    else if (SHT_SYMTAB == shdr.sh_type &&
	(name = elf_strptr(e, shstrndx, shdr.sh_name)) &&
	strcmp(name, ".symtab") == 0)
      *symtab_data = elf_getdata(scn, NULL);
    else if (SHT_STRTAB == shdr.sh_type &&
	(name = elf_strptr(e, shstrndx, shdr.sh_name)) &&
	strcmp(name, ".strtab") == 0)
      *strtab_data = elf_getdata(scn, NULL);
  return (0);
}

static inline s_sym	*new_sym(unsigned long addr, char *name)
{
  s_sym			*elem;

  if (NULL != (elem = malloc(sizeof(*elem))))
  {
    elem->addr = addr;
    elem->name = name;
  }
  return (elem);
}

s_list			*fill_list(int symtab_size, Elf_Data *symtab_data,
    Elf_Data *strtab_data)
{
  int			ndx;
  s_sym			*elem;
  char			*symstrtab;
  GElf_Sym		sym;
  s_list		*list;

  list = NULL;
  symstrtab = (char *) strtab_data->d_buf;
  ndx = 0;
  while (ndx < symtab_size &&
      gelf_getsym(symtab_data, ndx, &sym) &&
      (elem = new_sym(sym.st_value, strdup(&symstrtab[sym.st_name]))) &&
      (list = list_add(&list, elem)))
    ++ndx;
  if (ndx < symtab_size)
    fprintf(stderr, "error getting symbol\n");
  return (list);
}

s_list			*my_nm(char *filename)
{
  Elf			*e;
  Elf_Data		*symtab_data;
  Elf_Data		*strtab_data;
  s_list		*list;
  int			fd;
  int			symtab_size;

  symtab_data = strtab_data = NULL;
  if (NULL == (e = read_elf(&fd, filename)))
    return (NULL);
  if (0 != get_symdata(e, &symtab_data, &strtab_data) ||
      !symtab_data || !strtab_data)
  {
    elf_end(e);
    close(fd);
    fprintf(stderr, "no symbol found\n");
    return (NULL);
  }
  symtab_size = symtab_data->d_size /
    ((gelf_getclass(e) == ELFCLASS32) ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym));
  list = fill_list(symtab_size, symtab_data, strtab_data);
  elf_end(e);
  close(fd);
  return (list);
}
